// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Common;
using System.Diagnostics;
using System.Web.Services.Protocols;
using System.Xml;

namespace Microsoft.TeamFoundation.Migration.Toolkit.VC
{
    /// <summary>
    /// The message/warning/error callback
    /// </summary>
    /// <param name="message">The string to log</param>
    public delegate void ProcessorCallback(string message);

    /// <summary>
    /// The basic TFS analysis algorithm delegate type
    /// </summary>
    /// <param name="change">The change being processed</param>
    /// <param name="grouping">The change grouping this change is to be a part of</param>
    public delegate void TfsAnalysisAlgorithm(Change change, ChangeGrouping grouping);

    public abstract class TfsAnalysisAlgorithms
    {
        /// <summary>
        /// Called when the requested change type is unknown.  This indicates a previously unseen change operation and means that a
        /// new algorithm method needs to be created.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Unhandled(Change change, ChangeGrouping grouping)
        {
            if (change == null)
            {
                throw new ArgumentNullException("change");
            }

            throw new VersionControlMigrationException(
                string.Format(
                    MigrationToolkitVCResources.Culture,
                    MigrationToolkitVCResources.UnknownChangeTypeDuringAnalysis,
                    change.Item.ServerItem,
                    change.Item.ChangesetId,
                    change.ChangeType)
            );
        }

        /// <summary>
        /// Add a merge action into the change grouping
        /// action.SourceItem -> merge to item in source Tfs; 
        /// action.BranchFromVersion -> merge starting version of the item on source tfs
        /// action.MergeVersionTo -> merge end version of the item on source tfs
        /// action.TargetSourceItem -> merge from item on target system; 
        /// action.TargetTargetItem -> merge to item on target system
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Merge(Change change, ChangeGrouping grouping)
        {
            if (change.Item.DeletionId == 0)
            {
                validateChangeAndGroup(change, grouping);

                bool changeToAddOnBranchSourceNotFound = Session.GetValue<bool>("ChangeToAddOnBranchSourceNotFound", false);

                IMigrationAction action = createMigrationAction(change.Item, grouping, ChangeAction.Merge);

                VersionSpec changeSpec = new ChangesetVersionSpec(change.Item.ChangesetId);

                ChangesetMergeDetails mergeDetails;

                try
                {
                    mergeDetails = change.Item.VersionControlServer.QueryMergesWithDetails(
                                                            null,
                                                            null,
                                                            0,
                                                            change.Item.ServerItem,
                                                            changeSpec,
                                                            change.Item.DeletionId,
                                                            changeSpec,
                                                            changeSpec,
                                                            action.Recursive ? RecursionType.Full : RecursionType.None);
                }
                catch (XmlException xe)
                {
                    throw new VersionControlMigrationException(
                               string.Format(
                                   MigrationToolkitVCResources.Culture,
                                   MigrationToolkitVCResources.ClientNotCompatible), xe);

                }
                catch (VersionControlException vce)
                {
                    if (vce.InnerException is SoapException)
                    {
                        throw new VersionControlMigrationException(
                                   string.Format(
                                       MigrationToolkitVCResources.Culture,
                                       MigrationToolkitVCResources.ServerNotCompatible), vce);
                    }
                    else
                    {
                        throw;
                    }
                }

                bool mergeContributorFound = false;

                ItemMerge mergeVersionToItem = null;

                if (mergeDetails.MergedItems != null && mergeDetails.MergedItems.Length > 0)
                {
                    mergeVersionToItem = mergeDetails.MergedItems[mergeDetails.MergedItems.Length - 1];
                    Item contributor = change.Item.VersionControlServer.GetItem(mergeVersionToItem.SourceItemId, mergeVersionToItem.SourceVersionFrom, false);

                    action.TargetSourceItem = CreateItemIfMapped(contributor);
                    if (action.TargetSourceItem == null)
                    {
                        if (changeToAddOnBranchSourceNotFound)
                        {
                            TraceManager.TraceWarning(
                                "The merge action will be skipped as the merge source {0} of item {1} is not mapped in session {2}.",
                                contributor.ServerItem, change.Item.ServerItem, Session.Id);
                            return;
                        }
                        else
                        {
                            throw new VersionControlMigrationException(
                                string.Format(
                                    MigrationToolkitVCResources.Culture,
                                    MigrationToolkitVCResources.MergeSourceNotMapped,
                                    contributor.ServerItem,
                                    change.Item.ServerItem,
                                    Session.Id));
                        }
                    }

                    // Test to see whether the item exists at the spcific version.
                    try
                    {
                        change.Item.VersionControlServer.GetItem(contributor.ServerItem, new ChangesetVersionSpec(mergeDetails.MergedItems[0].TargetVersionFrom));
                        mergeContributorFound = true;
                    }
                    catch (VersionControlException)
                    {
                        // The merge contributor cannot be queried. 
                    }
                }
                if (mergeContributorFound)
                {
                    action.Version = mergeDetails.MergedItems[0].SourceVersionFrom.ToString(CultureInfo.InvariantCulture);
                    action.MergeVersionTo = mergeVersionToItem.SourceVersionFrom.ToString(CultureInfo.InvariantCulture);
                    
                    // For rename/undelete and merge change, we should use the item name before rename as the merge to item name.
                    Item previous = change.Item.VersionControlServer.GetItem(change.Item.ItemId, change.Item.ChangesetId - 1, false);
                    if (previous != null)
                    {
                        action.TargetTargetItem = CreateMigrationItem(previous);
                        grouping.AddAction(action);
                    }
                }
                else
                {
                    if (changeToAddOnBranchSourceNotFound)
                    {
                        TraceManager.TraceWarning("The merge operation will be skipped as the merge contributor for item {0};c{1} cannot be determined.", 
                            change.Item.ServerItem, change.Item.ChangesetId);
                    }
                    else
                    {
                        Session.OnAnalysisWarning(new VersionControlEventArgs(grouping,
                            string.Format(
                                MigrationToolkitVCResources.Culture,
                                MigrationToolkitVCResources.MergeContributorNotFound,
                                change.Item.ServerItem, change.Item.ChangesetId),
                            SystemType.Tfs));
                    }
                }
            }
            else
            {
                Session.OnAnalysisWarning(new VersionControlEventArgs(grouping,
                    string.Format(
                        MigrationToolkitVCResources.Culture,
                        MigrationToolkitVCResources.SkipDeletedMerge,
                        change.Item.ServerItem),
                    SystemType.Tfs));
            }
        }

        /// <summary>
        /// A delete undelete is ignored.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void DeleteUndelete(Change change, ChangeGrouping grouping)
        {
            // do nothing.
        }

        /// <summary>
        /// Adds an undelete operation to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Undelete(Change change, ChangeGrouping grouping)
        {
            validateChangeAndGroup(change, grouping);
            Item item = change.Item;

            // For a undelete action, previousItem is the delete version of the item. 
            // This version is needed if the path being undeleted contains several deleted items.
            Item previousItem = change.Item.VersionControlServer.GetItem(change.Item.ItemId, change.Item.ChangesetId - 1, false);

            if (previousItem == null)
            {
                previousItem = item;
            }

            if (IsItemMapped(item))
            {
                IMigrationAction action = createMigrationAction(item, grouping, ChangeAction.Undelete);
                action.TargetSourceItem = CreateItemIfMapped(previousItem);
                action.Version = previousItem.ChangesetId.ToString(CultureInfo.InvariantCulture);
                grouping.AddAction(action);
            }
        }

        /// <summary>
        /// Calls Edit and Undelete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void EditUndelete(Change change, ChangeGrouping grouping)
        {
            Undelete(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Adds a delete operation to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Delete(Change change, ChangeGrouping grouping)
        {
            validateChangeAndGroup(change, grouping);
            IMigrationAction deleteAction = createMigrationAction(change.Item, grouping, ChangeAction.Delete);
            
            Item previous = change.Item.VersionControlServer.GetItem(change.Item.ItemId, change.Item.ChangesetId - 1, false);
            if (previous != null)
            {
                deleteAction.TargetSourceItem = CreateMigrationItem(previous);
            }
            else
            {
                deleteAction.TargetSourceItem = deleteAction.TargetTargetItem;
            }
            grouping.AddAction(deleteAction);
        }

        /// <summary>
        /// None is not a valid change type. We will change it to Edit action. 
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void None(Change change, ChangeGrouping grouping)
        {
            Session.OnAnalysisWarning(
                new VersionControlEventArgs(
                    grouping,                 
                    string.Format(
                        MigrationToolkitVCResources.Culture,
                        MigrationToolkitVCResources.NoneChangeToEditDuringAnalysis,
                        change.Item.ServerItem,
                        change.Item.ChangesetId),
                    SystemType.Tfs));
            Edit(change, grouping);
        }

        /// <summary>
        /// Adds an edit action to the change grouping
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Edit(Change change, ChangeGrouping grouping)
        {
            validateChangeAndGroup(change, grouping);
            IMigrationAction action = createMigrationAction(change.Item, grouping, ChangeAction.Edit);

            Item previous = change.Item.VersionControlServer.GetItem(change.Item.ItemId, change.Item.ChangesetId - 1, false);
            if (previous != null)
            {
                action.TargetSourceItem = CreateMigrationItem(previous);
            }
            else
            {
                action.TargetSourceItem = action.TargetTargetItem;
            }
            grouping.AddAction(action);
        }

        /// <summary>
        /// Calls MergeUndelete and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void EditUndeleteMerge(Change change, ChangeGrouping grouping)
        {
            MergeUndelete(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Adds an add action to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Add(Change change, ChangeGrouping grouping)
        {
            validateChangeAndGroup(change, grouping);
            grouping.AddAction(createMigrationAction(change.Item, grouping, ChangeAction.Add));
        }

        /// <summary>
        /// Calls Branch and Merge to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void BranchMerge(Change change, ChangeGrouping grouping)
        {
            Branch(change, grouping);
            Merge(change, grouping);
        }

        // allowDeleted is used to filter out the case where deleted items are brought along with a direction rename.
        // in that case we don't want to pend the actions in the target system since they don't exist there.
        private void rename(Change change, ChangeGrouping grouping, bool allowDeleted)
        {
            validateChangeAndGroup(change, grouping);

            if (allowDeleted || change.Item.DeletionId == 0)
            {
                IMigrationAction action = grouping.CreateAction();

                action.ItemType = change.Item.ItemType;

                action.SourceItem = new TfsMigrationItem(change.Item, Session);

                Item previous = change.Item.VersionControlServer.GetItem(change.Item.ItemId, change.Item.ChangesetId - 1, false);

                action.TargetSourceItem = CreateItemIfMapped(previous);
                action.TargetTargetItem = CreateMigrationItem(change.Item);

                // if the source item of the rename is null it is not mapped.  If it's not mapped
                // we simply perform an add.
                if (action.TargetSourceItem != null)
                {
                    action.Action = ChangeAction.Rename;
                }
                else
                {
                    action.Action = ChangeAction.Add;
                }

                grouping.AddAction(action);
            }
            else
            {
                TraceManager.TraceInformation("Skipping deleted item carried over with a rename");
            }
        }


        /// <summary>
        /// Adds a rename action to the change grouping.  If the source operation of the rename operation is not mapped
        /// an add operation is added to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Rename(Change change, ChangeGrouping grouping)
        {
            rename(change, grouping, false);
        }

        /// <summary>
        /// Calls Rename and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameEdit(Change change, ChangeGrouping grouping)
        {
            Rename(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Calls Rename and Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameDeleteMerge(Change change, ChangeGrouping grouping)
        {
            Rename(change, grouping);
            Delete(change, grouping);
        }

        /// <summary>
        /// Calls Merge and Rename to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameMerge(Change change, ChangeGrouping grouping)
        {
            Merge(change, grouping);
            Rename(change, grouping);
        }

        /// <summary>
        /// Calls RenameMerge and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameEditMerge(Change change, ChangeGrouping grouping)
        {
            RenameMerge(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// If the previous item (the source item of the rename) is mapped an undelete action is added and Rename is called to 
        /// add the actions to the change grouping.  If the previous item is not mapped Add is called to add the appropriate 
        /// actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameUndelete(Change change, ChangeGrouping grouping)
        {
            Rename(change, grouping);
            Undelete(change, grouping);
        }


        /// <summary>
        /// Calls RenameUndelete and Merge to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameUndeleteMerge(Change change, ChangeGrouping grouping)
        {
            RenameUndelete(change, grouping);
            Merge(change, grouping);
        }

        /// <summary>
        /// Calls RenameUndelete, Merge and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameEditUndeleteMerge(Change change, ChangeGrouping grouping)
        {
            RenameUndelete(change, grouping);
            Merge(change, grouping);
            Edit(change, grouping);
        }


        /// <summary>
        /// Add a branch action to the change grouping. 
        /// action.SourceItem -> branched to item in source Tfs; 
        /// action.BranchFromVersion -> branch parent item version on source tfs
        /// action.TargetSourceItem -> branch parent on target system; 
        /// action.TargetTargetItem -> branched to item on target system
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void Branch(Change change, ChangeGrouping grouping)
        {
            validateChangeAndGroup(change, grouping);

            bool changeToAddOnBranchSourceNotFound = Session.GetValue<bool>("ChangeToAddOnBranchSourceNotFound", false);

            Item parentItem = GetBranchParent(change);

            if (parentItem == null)
            {
                if (changeToAddOnBranchSourceNotFound)
                {
                    TraceManager.TraceWarning("The branch operation will be changed to add as the branch parent {0};c{1} is not migrated.",
                        change.Item.ServerItem, change.Item.ChangesetId);
                    Add(change, grouping);
                }
                else
                {
                    Session.OnAnalysisError(new VersionControlEventArgs(
                        string.Format(MigrationToolkitVCResources.Culture, MigrationToolkitVCResources.BranchParentNotFound, 
                            change.Item.ServerItem, change.Item.ChangesetId), 
                        SystemType.Tfs));
                }
                return;
            }

            IMigrationAction action = createMigrationAction(change.Item, grouping, ChangeAction.Branch);

            action.TargetSourceItem = CreateItemIfMapped(parentItem);
            if (action.TargetSourceItem == null)
            {
                if (changeToAddOnBranchSourceNotFound)
                {
                    TraceManager.TraceWarning("The branch source {0} of item {1} is not mapped in session {2}. The action will be changed to add.",
                        parentItem.ServerItem, change.Item.ServerItem, Session.Id);
                    Add(change, grouping);
                }
                else
                {
                    throw new VersionControlMigrationException(
                        string.Format(
                            MigrationToolkitVCResources.Culture,
                            MigrationToolkitVCResources.BranchSourceNotMapped,
                            parentItem.ServerItem,
                            change.Item.ServerItem,
                            Session.Id)
                    );
                }
                return;
            }
            action.Version = parentItem.ChangesetId.ToString(CultureInfo.InvariantCulture);
            action.Recursive = true;
            grouping.AddAction(action);            
        }

        /// <summary>
        /// Return the parent item of a given branch change
        /// </summary>
        /// <param name="change"></param>
        /// <returns></returns>
        private Item GetBranchParent(Change change)
        {
            VersionControlServer tfsClient = change.Item.VersionControlServer;

            ChangesetVersionSpec version = new ChangesetVersionSpec(change.Item.ChangesetId);

            BranchHistoryTreeItem[][] history = tfsClient.GetBranchHistory(new ItemSpec[] 
                { new ItemSpec(change.Item.ServerItem, RecursionType.None, change.Item.DeletionId) }, version);

            if ((history == null) || (history.Length == 0) || (history[0].Length == 0))
            {
                return null;
            }
            else
            {
                BranchHistoryTreeItem item = history[0][0].GetRequestedItem();
                return item.Relative.BranchFromItem;
            }
        }


        /// <summary>
        /// Calls Merge and Undelete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void MergeUndelete(Change change, ChangeGrouping grouping)
        {
            Merge(change, grouping);
            Undelete(change, grouping);
        }

        /// <summary>
        /// Calls Merge and Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void MergeDelete(Change change, ChangeGrouping grouping)
        {
            // In whidbey and Sp1, you cannot merge a delete action on an item.
            //Merge(change, grouping);
            Delete(change, grouping);
        }

        /// <summary>
        /// Calls Branch and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void BranchEdit(Change change, ChangeGrouping grouping)
        {
            Branch(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Calls Branch and Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void BranchDelete(Change change, ChangeGrouping grouping)
        {
            Branch(change, grouping);
            Delete(change, grouping);
        }

        /// <summary>
        /// Calls BranchMerge and Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void BranchMergeDelete(Change change, ChangeGrouping grouping)
        {
            BranchMerge(change, grouping);
            Delete(change, grouping);
        }

        /// <summary>
        /// Calls BranchMerge and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void BranchMergeEdit(Change change, ChangeGrouping grouping)
        {
            BranchMerge(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Calls Merge and DeleteUndelete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void MergeDeleteUndelete(Change change, ChangeGrouping grouping)
        {
            Merge(change, grouping);
            DeleteUndelete(change, grouping);
        }

        /// <summary>
        /// Calls RenameUndelete and Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameDeleteUndelete(Change change, ChangeGrouping grouping)
        {
            Undelete(change, grouping);
            RenameDelete(change, grouping);
        }

        /// <summary>
        /// Calls RenameUndelete and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameEditUndelete(Change change, ChangeGrouping grouping)
        {
            RenameUndelete(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Calls MergeUndelete and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void MergeEditUndelete(Change change, ChangeGrouping grouping)
        {
            MergeUndelete(change, grouping);
            Edit(change, grouping);
        }


        /// <summary>
        /// Calls Merge and Edit to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void MergeEdit(Change change, ChangeGrouping grouping)
        {
            Merge(change, grouping);
            Edit(change, grouping);
        }

        /// <summary>
        /// Performs the rename operation using the same rules as the Rename method (without calling Rename) and then
        /// calls Delete to add the appropriate actions to the change grouping.
        /// </summary>
        /// <param name="change">The change being processed</param>
        /// <param name="grouping">The change grouing this change is to be a part of</param>
        public virtual void RenameDelete(Change change, ChangeGrouping grouping)
        {
            rename(change, grouping, true);
            Delete(change, grouping);
        }

        /// <summary>
        /// Determines whether the specified TFS Target is mapped in the current migration session.  This is used
        /// during rename to determine which actions to create.
        /// </summary>
        /// <param name="tfsItem">The item to check.</param>
        /// <returns>True if the item is mapped, false otherwise.</returns>
        protected abstract bool IsItemMapped(Item tfsItem);

        /// <summary>
        /// Creates a new migration item using the Target as the input.
        /// </summary>
        /// <param name="tfsItem">The TFS Target represented by the migration item.</param>
        /// <returns>The created IMigrationItem instance</returns>
        protected abstract IMigrationItem CreateMigrationItem(Item tfsItem);

        /// <summary>
        /// Returns the current VersionControlSession instance.
        /// </summary>
        protected abstract VersionControlSession Session
        {
            get;
        }

        private static void validateChangeAndGroup(Change change, ChangeGrouping grouping)
        {
            if (change == null)
            {
                throw new ArgumentNullException("change");
            }

            if (grouping == null)
            {
                throw new ArgumentNullException("grouping");
            }

            Debug.Assert(grouping.Status != ChangeStatus.Complete);
        }


        protected IMigrationItem CreateItemIfMapped(Item tfsItem)
        {
            if (IsItemMapped(tfsItem))
            {
                return CreateMigrationItem(tfsItem);
            }

            return null;
        }


        private IMigrationAction createMigrationAction(
            Item changeItem,
            ChangeGrouping grouping,
            ChangeAction changeType)
        {
            IMigrationAction action = grouping.CreateAction();
            action.ItemType = changeItem.ItemType;

            action.SourceItem = new TfsMigrationItem(changeItem, Session);
            action.TargetTargetItem = CreateMigrationItem(changeItem);
            action.Action = changeType;

            return action;
        }


    }
}